[2018_VolgaCTF] [REV] Crack Me¶
문제 내용¶
Just do it.
문제 풀이¶
파일 다운로드 시 실행 파일 Crackme.exe와 암호화 텍스트 Crackme.txt가 주어진다. Crackme.exe 실행 시 다음과 같은 화면이 출력된다.
Incorrect arguments
Usage: CrackMe.exe <input_file> <output_file> <user_pass_phrase> <mode>
ilspy로 디컴파일한 뒤, 출력되는 문자열을 검색하면 다음 코드 부분을 확인할 수 있다.
// CrackMe.Program
private static void Main(string[] args)
{
if (args.Length != 4)
{
Console.WriteLine("Incorrect arguments");
Console.WriteLine("Usage: CrackMe.exe <input_file> <output_file> <user_pass_phrase> <mode>");
return;
}
string text = args[0];
string outputFile = args[1];
string userPassword = args[2];
string a = args[3];
if (!File.Exists(text))
{
Console.WriteLine("Input file doesn't exist");
return;
}
CryptoOperation cryptoOperation = new CryptoOperation();
cryptoOperation.FileName = text;
cryptoOperation.UserPassword = userPassword;
if (a == "encrypt")
{
Program.Encrypt(cryptoOperation, outputFile);
return;
}
if (!(a == "decrypt"))
{
Console.WriteLine("Incorrect mode");
Console.WriteLine("Use encrypt or decrypt");
return;
}
Program.Decrypt(cryptoOperation, outputFile);
}
mode 인자값에 따라 암호화 복호화가 가능하며, 주어진 CrackMe.txt파일을 복호화하는게 이번 문제에 핵심으로 보인다.
// CrackMe.CryptoOperation
public byte[] DecryptFile()
{
byte[] result = null;
using (AesCryptoServiceProvider aesCryptoServiceProvider = new AesCryptoServiceProvider())
{
aesCryptoServiceProvider.Key = this.UserKey;
aesCryptoServiceProvider.IV = this.IV;
try
{
ICryptoTransform transform = aesCryptoServiceProvider.CreateDecryptor(aesCryptoServiceProvider.Key, aesCryptoServiceProvider.IV);
FileInfo fileInfo = new FileInfo(this.FileName);
using (MemoryStream memoryStream = new MemoryStream(this.ProcessingBytes))
{
using (CryptoStream cryptoStream = new CryptoStream(memoryStream, transform, CryptoStreamMode.Read))
{
using (BinaryReader binaryReader = new BinaryReader(cryptoStream))
{
result = binaryReader.ReadBytes((int)fileInfo.Length);
}
}
}
}
catch
{
Console.WriteLine("Failed to decrypt file {0}: wrong password.", this.FileName);
}
}
return result;
}
aes로 암복호화를 하고 있으며, Userkey값을 키값으로 쓰고 있다. UserKey값은 3번째 인자값을 다음과 같이 변행하여 적용한다.
// CrackMe.CryptoOperation
public string UserPassword
{
set
{
this.KeyLength = 16;
byte[] userKey = MD5.Create().ComputeHash(Encoding.UTF8.GetBytes(value));
this.UserKey = this.CombineKeys(userKey);
}
}
// CrackMe.CryptoOperation
private byte[] CombineKeys(byte[] UserKey)
{
AppSettings appSettings = new AppSettings();
byte[] expr_16 = Encoding.UTF8.GetBytes(appSettings.DefaultKey);
long num = BitConverter.ToInt64(expr_16, 0);
long num2 = BitConverter.ToInt64(expr_16, 8);
long num3 = BitConverter.ToInt64(UserKey, 0);
long num4 = BitConverter.ToInt64(UserKey, 8);
long num5 = num ^ num3;
long num6 = num2 ^ num4;
long num7 = (~num & num3) | (~num3 & num);
long num8 = (~num2 & num4) | (~num4 & num2);
int num9 = BitConverter.ToInt32(BitConverter.GetBytes(num5), 0);
int num10 = BitConverter.ToInt32(BitConverter.GetBytes(num5), 4);
int num11 = BitConverter.ToInt32(BitConverter.GetBytes(num6), 0);
int num12 = BitConverter.ToInt32(BitConverter.GetBytes(num6), 4);
num9 >>= 2;
num10 >>= 2;
num9 <<= 1;
num10 <<= 1;
num12 = num9 << 1;
num11 >>= 2;
num11 = num9 << 1;
num12 >>= 2;
if (~(num9 & num12) == (~num9 | ~num12))
{
num11 = num10;
if (~(num9 & num12) == (~num9 | ~num12))
{
num10 = num12;
}
else
{
num12 = num10;
}
num9 = ~num12;
}
else
{
num11 = num9;
if (~(~num7) == num5 && ~(~num8) == num6)
{
num10 = num12;
}
else
{
num12 = num10;
}
num9 = ~num10;
}
num9 = ~num9;
byte[] bytes = BitConverter.GetBytes(num9);
byte[] bytes2 = BitConverter.GetBytes(num10);
byte[] bytes3 = BitConverter.GetBytes(num11);
byte[] bytes4 = BitConverter.GetBytes(num12);
byte[] array = new byte[16];
for (int i = 0; i < 4; i++)
{
array[i] = bytes[i];
array[i + 4] = bytes2[i];
array[i + 8] = bytes3[i];
array[i + 12] = bytes4[i];
}
return array;
}
위 코드는 보기엔 복잡한 계산식이지만 트릭이 있다. 계산식 중 "num ^ num3"은 "~num & num3) | (~num3 & num)"과 같으며, "num2 ^ num4"도 역시 "(~num2 & num4) | (~num4 & num2)"과 같다. 그리고 if 조건문이 존재하는데 "(~(num9 & num12) == (~num9 | ~num12))"은 어떤 값이든 항상 참으로 else 문이 필요없다. 그리고 if 조건문 안에 추가 if 조건문이 존재하는데 "(~(num9 & num12) == (~num9 | ~num12))"은 어떤 값이든 항상 거짓으로 else문으로 가게된다. 해당 내용에 맞게 위 코드는 다음과 같이 정리할 수 있다.
// CrackMe.CryptoOperation
private byte[] CombineKeys(byte[] UserKey)
{
AppSettings appSettings = new AppSettings();
byte[] expr_16 = Encoding.UTF8.GetBytes(appSettings.DefaultKey);
long num = BitConverter.ToInt64(expr_16, 0);
long num2 = BitConverter.ToInt64(expr_16, 8);
long num3 = BitConverter.ToInt64(UserKey, 0);
long num4 = BitConverter.ToInt64(UserKey, 8);
long num5 = num ^ num3;
long num6 = num2 ^ num4;
long num7 = num ^ num3;
long num8 = num2 ^ num4;
int num9 = BitConverter.ToInt32(BitConverter.GetBytes(num5), 0);
int num10 = BitConverter.ToInt32(BitConverter.GetBytes(num5), 4);
int num11 = BitConverter.ToInt32(BitConverter.GetBytes(num6), 0);
int num12 = BitConverter.ToInt32(BitConverter.GetBytes(num6), 4);
num9 >>= 2;
num10 >>= 2;
num9 <<= 1;
num10 <<= 1;
num12 = num9 << 1;
num11 >>= 2;
num11 = num9 << 1;
num12 >>= 2;
num11 = num10;
num12 = num10;
num9 = ~num12;
num9 = ~num9;
byte[] bytes = BitConverter.GetBytes(num9); # num9 = num12
byte[] bytes2 = BitConverter.GetBytes(num10); # num10 = (BitConverter.ToInt32(BitConverter.GetBytes(BitConverter.ToInt64(expr_16, 0) ^ BitConverter.ToInt64(UserKey, 0)), 4)>>2) << 1
byte[] bytes3 = BitConverter.GetBytes(num11); # num11 = num10
byte[] bytes4 = BitConverter.GetBytes(num12); # num12 = num10
byte[] array = new byte[16];
for (int i = 0; i < 4; i++)
{
array[i] = bytes[i];
array[i + 4] = bytes2[i];
array[i + 8] = bytes3[i];
array[i + 12] = bytes4[i];
}
return array;
}
결국 리턴값 array에 들어갈 값은 4바이트가 연속되는 값들이 들어가게 된다.
ex>
31323334 31323334 31323334 31323334
41424344 41424344 41424344 41424344
해당 키값을 획득하기 위해 브루트포싱 진행 - 오른쪽으로 2번 시프트 연산 왼쪽으로 1번 시프트 연산
- 오른쪽 1bit는 0
- 왼쪽 1bit는 0
from Crypto.Cipher import AES
import struct
f = open("CrackMe.txt","rb")
data = f.read()
f.close()
iv = data[:16]
data = data[16:]
for i in range(0x10000000,0x80000000,2):
key = struct.pack('>I',i)
aes = AES.new(key*4, AES.MODE_CBC, iv)
dec = aes.decrypt(data)
if "Volga" in str(dec):
print(key)
print(dec)
break